package com.tianji.learning.controller.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.*;
import com.tianji.learning.controller.service.ILearningLessonService;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.domain.vo.LearningLessonVO;
import com.tianji.learning.domain.vo.LearningPlanPageVO;
import com.tianji.learning.domain.vo.LearningPlanVO;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 学生课程表 服务实现类
 * </p>
 *
 * @author zt
 * @since 2024-08-21
 */
@Service
@SuppressWarnings("ALL")
@RequiredArgsConstructor
@Slf4j
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;
    private final CatalogueClient catalogueClient;
    private final LearningRecordMapper recordMapper;
    @Override
    @Transactional
    public void addUserLessons(Long userId, List<Long> courseIds) {
        //1.通过调用courseClient查询课程信息列表
        //courseClient是课程服务的openFegin接口
        //getSimpleInfoList()是根据课程id集合返回课程简单信息列表
        List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(courseIds);
        if(CollUtils.isEmpty(cInfoList)){
            //课程不存在，无法添加
            log.error("课程不存在，无法添加");
            return;
        }
        //2.根据cInfoList信息填充课表LearningLession信息
        //LearningLession才是最终要保存到数据库的信息
        //需要填充的信息包括：课表过期时间、用户id、课程id
        List<LearningLesson> list = new ArrayList<>(cInfoList.size());
        for (CourseSimpleInfoDTO cInfo : cInfoList) {
            LearningLesson lesson = new LearningLesson();
            //计算过期时间并填入，因为CourseSimpleInfoDTO包含的是课程的有效时间，不是课程结束时间
            Integer validDuration = cInfo.getValidDuration();
            if(validDuration!=null&&validDuration>0){
                lesson.setExpireTime(LocalDateTime.now().plusMonths(validDuration));
            }
            //填入userId和courseId
            lesson.setUserId(userId);
            lesson.setCourseId(cInfo.getId());
            list.add(lesson);
        }
        //3.批量新增
        //saveBatch是mp在service层提供的保存服务，也可以使用mapper层的insert来进行保存
        this.saveBatch(list);
    }

    /**
     *分页查询我的课表
     * @param query
     * @return
     */
    @Override
    public PageDTO<LearningLessonVO> queryMyLession(PageQuery query) {
        //获取用户id，用于数据的查询
        Long userId = UserContext.getUser();
        //分页查询
        //select * from learning_lession where user_id = #{userId} order by latest_learn_time limit 0,5
        //this.lambdaQuery()是Mybatis-Plus提供的查询器对象
        //page()方法需要传入一个Mybatis-Plus提供的一个分页对象Page，
        //query(PageQuery类)使用toMpPage方法可以将其转化为MP的page方法需要的page分页对象
        //page分页对象里面包含了PageNo页码，PageSize页大小、排序字段和排序方式
        Page<LearningLesson> page = this.lambdaQuery().eq(LearningLesson::getUserId, userId)
                //"latest_learn_time"是排序字段，false表示不是升序排序，表示倒序，即最近学习的课程排在前面，页码和页号前端已经传入了
                .page(query.toMpPage("latest_learn_time", false));
        //records是分页查询后的数据部分的一个List集合，就是一个从page中取数据的操作
        //取数据的目的是将查询的结果转成vo类返回给前端
        List<LearningLesson> records = page.getRecords();
        if(CollUtils.isEmpty(records)){
            //查询结果为空，则直接返回一个空的分页
            return PageDTO.empty(page);
        }
        //po和vo的差距是vo需要课程名，课程课程封面，章节数量
        //这些信息要在课程服务里面去获取，所以需要调用课程的fegin接口
        //获取课程id的集合
        Set<Long> cSet = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
        List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(cSet);
        //转化成map类型是为了方便数据的取用
        Map<Long, CourseSimpleInfoDTO> cInfoMap = cInfoList.stream().collect(Collectors.toMap(CourseSimpleInfoDTO::getId, cInfo -> cInfo));
        //po转vo
        List<LearningLessonVO> list = new ArrayList<>();//可以指定大小也可以不指定，ArrayList可以动态扩容，不过指定大小效率更高
        for (LearningLesson record : records) {
            //复制po到vo
            LearningLessonVO vo = BeanUtils.copyBean(record, LearningLessonVO.class);
            CourseSimpleInfoDTO infoDTO = cInfoMap.get(vo.getCourseId());
            //将课程名称，课程封面，课程章节数从课程信息中设置到vo中
            vo.setCourseName(infoDTO.getName());
            vo.setCourseCoverUrl(infoDTO.getCoverUrl());
            vo.setSections(infoDTO.getSectionNum());
            list.add(vo);
        }
        //返回结果，PageDTO.of()方法会获取page中的total和pages和list的结果
        //即return new PageDTO<>(page.getTotal(), page.getPages(), list)
        //本质就是对返回结果做了简化
        return PageDTO.of(page,list);
    }

    /**
     * 查询正在学习的课表
     * @return
     */
    @Override
    public LearningLessonVO queryMyCurrentLession() {
        Long userId = UserContext.getUser();
        //select * from learning_lession where user_id = #{userId} and status = 1 order by latest_learn_time desc limit 1;
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, 1)//status=1表示正在学习
                .orderByDesc(LearningLesson::getLatestLearnTime)//这里没有page，只能手动去拼接排序和分页了
                .last("limit 1")
                .one();
        if(lesson==null){
            return null;
        }
        //po转vo
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        //补充vo中缺失的信息，要从课程服务接口去找，后面两个参数表示是否需要目录信息和是否需要教师信息，这里都不需要
        CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(vo.getCourseId(), false, false);
        if(cInfo==null){
            throw new BadRequestException("课程信息不存在");
        }
        vo.setCourseName(cInfo.getName());
        vo.setCourseCoverUrl(cInfo.getCoverUrl());
        vo.setSections(cInfo.getSectionNum());
        //还需要courseAmount(课表中课程总数)数据，在数据库中查
        //select count(1) from learning_lession where user_id = #{userId}
        Integer count = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .count();
        vo.setCourseAmount(count);
        //latestSectionName(最近学习的小节名称)和latestSectionIndex(最近学习的小节序号)需要调用Fegin接口
        /*
     catalogueClient.batchQueryCatalogue
     * 根据目录id列表查询目录信息
     * @param ids 目录id列表
     * @return id列表中对应的目录基础信息
     */
        //查询小节信息
        List<CataSimpleInfoDTO> infoDTOList =//需要传入的是一个列表，这里将其封装成一个列表
                catalogueClient.batchQueryCatalogue(CollUtils.singletonList(lesson.getLatestSectionId()));
        if(CollUtils.isNotEmpty(infoDTOList)){
            vo.setLatestSectionName(infoDTOList.get(0).getName());
            vo.setLatestSectionIndex(infoDTOList.get(0).getCIndex());
        }
        return vo;
    }

    @Override
    public LearningLesson queryByUserAndCourseId(Long userId, Long courseId) {
        LearningLesson lesson = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        return lesson;
        
    }

    @Override
    public void createLearningPlan(Long courseId, Integer freq) {
        //1.获取当前登录的用户
        Long userId = UserContext.getUser();
        //2.查询课表中的指定课程有关的数据
        LearningLesson lesson = queryByUserAndCourseId(userId, courseId);
        AssertUtils.isNotNull(lesson,"课程信息不存在");
        //3.修改数据
        LearningLesson l = new LearningLesson();
        l.setId(lesson.getId());
        l.setWeekFreq(freq);
        if(lesson.getPlanStatus()== PlanStatus.NO_PLAN){
            l.setPlanStatus(PlanStatus.PLAN_RUNNING);
        }
        updateById(l);
    }
    public void createLearningPlan1(Long courseId, Integer freq){
        //从数据库中查找lession
        Long userId = UserContext.getUser();
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        if(lesson==null){
            throw new DbException("查找课表失败");
        }
        //创建一个新的计划，所有的数据都要改变，所以直接创建一个新对象，只是课表id不变
        LearningLesson newLesson = new LearningLesson();
        newLesson.setId(lesson.getId());
        newLesson.setWeekFreq(freq);
        if(lesson.getPlanStatus()==PlanStatus.NO_PLAN){
            newLesson.setPlanStatus(PlanStatus.PLAN_RUNNING);
        }
        this.updateById(newLesson);
    }
    @Override
    public LearningPlanPageVO queryMyPlan(PageQuery query) {
        LearningPlanPageVO result = new LearningPlanPageVO();
        //1.获取当前登录用户
        Long userId = UserContext.getUser();
        //2.获取本周起始时间
        LocalDate now = LocalDate.now();
        LocalDateTime begin = DateUtils.getWeekBeginTime(now);
        LocalDateTime end = DateUtils.getWeekEndTime(now);
        //3.查询总的统计数量
        //3.1本周总的已学习小节数量
        //select count(*) from learning_record where user_id = #{userId} and finished = 1 and finish_time between #{begin} and #{end}
        Integer weekFinished = recordMapper.selectCount(new LambdaQueryWrapper<LearningRecord>()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getFinished, true)
                .gt(LearningRecord::getFinishTime, begin)
                .lt(LearningRecord::getFinishTime, end));
        result.setWeekFinished(weekFinished);
        //3.2本周总的计划学习小节数(基于mp实现)
        //select sum(week_freq) from learning_lession where user_id = #{userId} and plan_status = 1 and status in (0,1)
        QueryWrapper<LearningLesson> wrapper = new QueryWrapper<>();
        wrapper.select("sum(week_freq) as plansTotal");
        wrapper.eq("user_id",userId);
        wrapper.eq("plan_status",PlanStatus.PLAN_RUNNING);
        wrapper.in("status",LessonStatus.NOT_BEGIN,LessonStatus.LEARNING);
        //map就是一个json，key就是planTotal，{planTotal=7}
        Map<String, Object> map = this.getMap(wrapper);
        //结果是BigDecimal类型，不能直接强转成Integer
        Integer weekTotalPlan = 0;
        if(map!=null&&map.get("plansTotal")!=null){
            weekTotalPlan = Integer.valueOf(map.get("planTotal").toString());
        }
        result.setWeekTotalPlan(weekTotalPlan);
        //TODO：3.3本周学习积分
        //4.查询分页数据
        //4.1分页查询课表信息以及学习计划信息
        Page<LearningLesson> page = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .page(query.toMpPage("latest_learning_time", false));//分页查询
        List<LearningLesson> records = page.getRecords();
        if(CollUtils.isEmpty(records)){
            LearningPlanPageVO vo = new LearningPlanPageVO();
            vo.setTotal(0L);
            vo.setPages(0L);
            vo.setList(CollUtils.emptyList());
            return vo;
        }
        //4.2查询课表对应的课程信息
        Map<Long, CourseSimpleInfoDTO> cMap = queryCourseSimpleInfoList(records);
        //4.3统计每一个课程本周已学习的小节数量
        //select lession_id, count(*) from learning_record where user_id = #{userId} and finish_time betweemn #{begin} and #{end} group by lession_id
        QueryWrapper<LearningRecord> rWrapper = new QueryWrapper<>();
        rWrapper.select("lession_id as lessionId, count(*) as userId");//仅是用userId去临时存储count(*),这样就不需要在去建一个专门的类了
        rWrapper.eq("user_id",userId);
        rWrapper.between("finish_time",begin,end);
        rWrapper.groupBy("lession_id");
        List<LearningRecord> learningRecords = recordMapper.selectList(rWrapper);
        Map<Long, Long> courseWeekFinishedNumMap = learningRecords.stream().collect(Collectors.toMap(LearningRecord::getLessonId, c -> c.getUserId()));
        //4.4封装VO返回
        List<LearningPlanVO> voList = new ArrayList<>(records.size());
        for (LearningLesson r : records) {
            // 4.4.1.拷贝基础属性到vo
            LearningPlanVO vo = BeanUtils.copyBean(r, LearningPlanVO.class);
            // 4.4.2.填充课程详细信息
            CourseSimpleInfoDTO cInfo = cMap.get(r.getCourseId());
            if (cInfo != null) {
                vo.setCourseName(cInfo.getName());
                vo.setSections(cInfo.getSectionNum());
            }
            // 4.4.3.每个课程的本周已学习小节数量
            vo.setWeekLearnedSections(courseWeekFinishedNumMap.getOrDefault(r.getId(),0L).intValue());
            voList.add(vo);
        }
        return result.pageInfo(page.getTotal(), page.getPages(), voList);
    }

    /***
     * 获取课程信息
     * @param records
     * @return
     */
    private Map<Long, CourseSimpleInfoDTO> queryCourseSimpleInfoList(List<LearningLesson> records) {
        // 3.1.获取课程id
        Set<Long> cIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
        // 3.2.查询课程信息
        List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(cIds);
        if (CollUtils.isEmpty(cInfoList)) {
            // 课程不存在，无法添加
            throw new BadRequestException("课程信息不存在！");
        }
        // 3.3.把课程集合处理成Map，key是courseId，值是course本身
        Map<Long, CourseSimpleInfoDTO> cMap = cInfoList.stream()
                .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));
        return cMap;
        }
    }
